from comment.forms import CommentForm
from comment.models import Comment
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.utils import IntegrityError
from django.http import JsonResponse, Http404
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse_lazy, reverse
from django.views import View
from django.views.generic import (
    ListView, DetailView, DeleteView,
)
from django.views.generic.edit import FormMixin, BaseCreateView, CreateView, UpdateView
from django_redis import get_redis_connection

from .forms import PostForm, CategoryForm, TagForm
from .mixins import OwnerMixin
from .models import Post, User, Category, Like, Tag


class PostDeleteView(LoginRequiredMixin, OwnerMixin, DeleteView):
    """文章删除"""
    model = Post
    template_name = 'blog/post_confirm_delete.html'
    success_url = reverse_lazy('blog:index')
    context_object_name = 'post'


class PostUpdateView(LoginRequiredMixin, OwnerMixin, UpdateView):
    """更新文章"""
    model = Post
    form_class = PostForm
    template_name = 'blog/post_update.html'

    def form_valid(self, form):
        form.instance.user = self.request.user

        # 保存类别，可以为空
        category = form.cleaned_data.get('category')
        if category:
            form.instance.category = category

        # 先保存Post对象（self.object有值）才能添加多对多关系数据
        resp = super().form_valid(form)
        # 保存标签，可以为空
        tags = form.cleaned_data.get('tags')
        if tags:
            for tag in tags:
                self.object.tags.add(tag)
        return resp

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        # 通过前端modal显示类别表单
        context['category_form'] = CategoryForm()

        # 获取用户相关的文章栏目，在模板中预先填充原先的值
        context['categories'] = Category.objects.filter(user=self.request.user)

        # 获取用户相关的标签，模板中预先填充原先的值
        context['tags'] = Tag.objects.filter(user=self.request.user)
        context['post_tags'] = self.object.tags.all()
        return context


class PostCreateView(LoginRequiredMixin, CreateView):
    form_class = PostForm
    template_name = 'blog/post_create.html'

    # 如果form.is_valid通过校验，则执行form_valid方法
    def form_valid(self, form):
        form.instance.user = self.request.user
        # 保存类别，可以为空
        category = form.cleaned_data.get('category')
        if category:
            form.instance.category = category

        # 先保存Post对象（self.object有值）才能添加多对多关系数据
        resp = super().form_valid(form)
        # 保存标签，可以为空
        tags = form.cleaned_data.get('tags')
        if tags:
            for tag in tags:
                self.object.tags.add(tag)
        return resp

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        # 通过前端modal显示类别表单
        context['category_form'] = CategoryForm()
        context['tag_form'] = TagForm()
        return context


class PostDetailView(FormMixin, DetailView):
    """文章详情页面"""
    template_name = 'blog/post_detail.html'
    context_object_name = 'post'
    form_class = CommentForm

    def get_queryset(self):
        return Post.objects.select_related('user').all()

    def get(self, request, *args, **kwargs):
        """重写get方法，更新文章浏览次数(num_views)，当访问详情页时"""
        self.object = self.get_object()

        # 递增访问量
        self.object.num_views += 1
        # update_fields=[]指定了数据库只更新num_views字段，优化执行效率
        self.object.save(update_fields=['num_views'])

        # 添加用户最近浏览记录
        if request.user.is_authenticated:
            conn = get_redis_connection('default')
            visited_key = 'visited_%d' % self.request.user.id
            # 移除列表中的post.slug
            conn.lrem(visited_key, 0, self.object.slug)
            # 把post.slug插入到列表的左侧
            conn.lpush(visited_key, self.object.slug)
            # 只保存用户最新浏览的5条信息
            conn.ltrim(visited_key, 0, 4)

        context = self.get_context_data(object=self.object)
        return self.render_to_response(context)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        # 关联三张表Comment,User,Profile
        comments = Comment.objects.select_related('user__profile').select_related('reply_to').filter(
            post__slug=self.kwargs.get('slug')
        )
        context['comments'] = comments
        return context


class PostListView(ListView):
    """文章列表页面，主页显示"""
    template_name = 'blog/index.html'
    context_object_name = 'posts'
    paginate_by = settings.PAGINATION_SIZE

    def get_queryset(self):
        # 执行python manage.py shell_plus --print-sql可以查看执行的SQL语句

        # .select_related方法可以进行关联查询，减少访问数据库频次，如:
        # index.html中获取用户名时(post.user.username)不会再次访问数据库，
        # 详情说明查看链接:
        # https://docs.djangoproject.com/en/3.0/ref/models/querysets/
        return Post.objects.select_related('user').all().order_by('-date_created')


class UserPostListView(ListView):
    """用户关联文章列表页面"""
    template_name = 'blog/user_posts.html'
    paginate_by = settings.PAGINATION_SIZE

    def get_queryset(self):
        user = get_object_or_404(User, username=self.kwargs.get('username'))
        # .select_related减少数据库查询
        return Post.objects.select_related('user').filter(user=user).order_by('-date_created')


class CategoryPostListView(ListView):
    """分类下的文章页面"""

    template_name = 'blog/category_posts.html'
    paginate_by = settings.PAGINATION_SIZE

    def get_queryset(self):
        category = get_object_or_404(Category, name=self.kwargs.get('category_name'))
        return Post.objects.filter(category=category).order_by('-date_created')


class ArchivePostListView(LoginRequiredMixin, ListView):
    template_name = 'blog/archive_posts.html'
    paginate_by = settings.PAGINATION_SIZE

    def get_queryset(self):
        try:
            date = self.kwargs.get('date')  # 2020-5
            year, month = date.split('-')
        except (ValueError, TypeError):
            # logging
            raise Http404
        else:
            if month[0] == '0':  # '05' -> '5'
                month = month[1]
            qs = Post.objects.filter(
                user=self.request.user,
                date_created__year=int(year),
                date_created__month=int(month)
            )
            return qs


class PostLikeView(View):
    """点赞"""

    def post(self, request, *args, **kwargs):
        if not request.user.is_authenticated:
            return redirect(reverse('account:login') + '?/next=%s' % request.path)

        post_slug = kwargs.get('slug', '')
        if not post_slug:
            return JsonResponse({
                'code': 1,
                'errors': ['出错']
            })
        else:
            try:
                post = Post.objects.get(slug=post_slug)
            except (Post.DoesNotExist, Post.MultipleObjectsReturned):
                return JsonResponse({'code': 1, 'errors': ['出错']})
            else:
                # 添加点赞记录
                try:
                    Like.objects.create(user=request.user, post=post)
                except IntegrityError:  # 联合唯一(user, post)，unique_together
                    return JsonResponse({'code': 1, 'errors': ['已经点赞过']})

                post.num_likes += 1
                post.save()
                return JsonResponse({'code': 0})


class CategoryCreateView(LoginRequiredMixin, BaseCreateView):
    """创建新的类别，该视图只处理POST请求，不显示文章类别表单HTML"""
    success_url = reverse_lazy('blog:create')  # 返回写文章页面
    form_class = CategoryForm

    def form_valid(self, form):
        form.instance.user = self.request.user
        return super().form_valid(form)


class TagsCreateView(LoginRequiredMixin, View):
    """创建新的标签，只处理POST请求"""

    def post(self, request):
        form = TagForm(request.POST)
        if form.is_valid():
            tag_names = form.cleaned_data['tags'].strip().split()  # ['Django', 'Python', 'Java']
            for tag_name in tag_names:
                Tag.objects.get_or_create(name=tag_name, user=self.request.user)
            return redirect(reverse('blog:create'))
